package server.factory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Entity;
import javax.persistence.EntityManager;

public abstract class MockFactory<T> {

	private static Map<Class<?>, MockFactory<?>> factories = new HashMap<Class<?>, MockFactory<?>>();
	static {
		register(new ServiceMockFactory());
		register(new StatusMockFactory());
		register(new ConsumptionMockFactory());
		register(new InsuranceMockFactory());
		register(new MaintenanceMockFactory());
		register(new UserMockFactory());
		register(new VehicleMockFactory());
		register(new FamilyMockFactory());
	}

	private final Class<T> domainClass;

	private int seed;

	private static void register(MockFactory<?> mockFactory) {

		factories.put(mockFactory.domainClass, mockFactory);
	}

	@SuppressWarnings("unchecked")
	public static <T> MockFactory<T> on(Class<T> domainClass) {

		MockFactory<?> factory = factories.get(domainClass);
		if (factory == null) {
			throw new IllegalStateException("Did you forget to register a mock factory for "
					+ domainClass.getClass().getName() + "?");
		}
		return (MockFactory<T>) factory;
	}

	protected MockFactory(Class<T> domainClass) {

		if (domainClass.getAnnotation(Entity.class) == null) {
			throw new IllegalArgumentException();
		}
		this.domainClass = domainClass;
	}

	/**
	 * Create several objects
	 * 
	 * @param entityManager
	 *            the entity manager, or null if the mocked objects should not be persisted
	 * @param count
	 *            the number of objects to create
	 * @return the created objects
	 */
	public List<T> createAndPersist(EntityManager entityManager, int count) {

		List<T> mocks = new ArrayList<T>(count);
		for (int x = 0; x < count; ++x) {
			mocks.add(createAndPersist(entityManager));
		}
		return mocks;
	}

	/**
	 * Create a single object
	 * 
	 * @param entityManager
	 *            the entity manager, or null if the mocked object should not be persisted
	 * @return the mocked object
	 */
	public T createAndPersist(EntityManager entityManager) {

		T mock = create();

		if (entityManager != null) {
			entityManager.persist(mock);
		}
		return mock;
	}

	public List<T> create(int count) {

		List<T> mocks = new ArrayList<T>(count);
		for (int x = 0; x < count; ++x) {
			mocks.add(create());
		}
		return mocks;
	}

	public T create() {

		T mock;
		try {
			mock = domainClass.newInstance();
			populate(++seed, mock);
		} catch (Exception e) {
			// must have a default constructor
			throw new IllegalStateException();
		}

		return mock;
	}

	/**
	 * Populate the given domain object with data
	 * 
	 * @param seed
	 *            a seed that may be used to create data
	 * @param mock
	 *            the domain object to populate
	 */
	protected abstract void populate(int seed, T mock);

}
